/*================================================================
*   Copyright (C) 2019 Guangzhou GYT Ltd. All rights reserved.
*   
*   文件名称：main.c
*   创 建 者：luhuadong
*   创建日期：2019年03月25日
*   描    述：免提回声消除功能
*
================================================================*/

#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <semaphore.h>
#include <errno.h>
#include <getopt.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include "main.h"

#define DEFAULT_LOCK_FILE    "/var/run/gytaecd.pid"
#define DEFAULT_CONF_FILE    "/etc/gytaecd.conf"
#define DEFAULT_LOG_FILE     "/tmp/gytaecd.log"

#define SND_CARD_NAME        "plughw:0,0"

#define FRAMES_PER_PERIOD    160
#define FRAMES_PER_BUFFER    640
#define SAMPLE_RATE          8000

#define HANDFREE_GPIO        0
#define HANDFREE_FIFO        1
#define HANDFREE_SIGNAL      2

#define FIFO_NAME            "/tmp/aec_ctrl_fifo"

/* Variable */
static struct pollfd   fds[1];                         /* GPIO value file handlers */
static int             handfree_fd ;                   /* gpio or fifo fd */

static ADT_Int16       out_stereo[FRAMES_PER_BUFFER];  /* only use period_size */
static pthread_mutex_t data_lock;                      /* 互斥锁的效果不好，应该使用环形缓冲区存储共享数据 */
static sem_t           handfree_sem;                   /* 用于免提信号同步 */
static sem_t           data_sem;                       /* 用于PCM数据同步 */
static FILE           *log_file;                       /* 日志文件指针 */

static IAECG4_Params AEC_Params = {
    
    /* Base Parameters */

    sizeof(IAECG4_Params),
    0,          /* LockCallback_t  */
    160,        /* FRAME_SIZE !!!  */
    0,          /* AntiHowlEnable  */
    8000,       /* SAMPLING_RATE   */
    4000,       /* SAMPLING_RATE/2 */
    0,          /* FixedBulkDelayMSec (modified by luhuadong at 20170323) */
    0,          /* TailSearchSamples */
    0,          /* InitialBulkDelay  */
    128,        /* ADT_Int16 ActiveTailLengthMSec    */
    128,        /* ADT_Int16 TotalTailLengthMSec, 32 */
    1,          /* ADT_Int16 txNLPAggressiveness, 0, 10 */
    20,         /* ADT_Int16 MaxTxLossSTdB, 0, 33 */
    15,         /* ADT_Int16 MaxTxLossDTdB, 0, 12 */
    4,          /* ADT_Int16 MaxRxLossdB, 12 */
    0,          /* InitialRxOutAttendB */
    -85,        /* ADT_Int16 TargetResidualLeveldBm */
    -90,        /* ADT_Int16 MaxRxNoiseLeveldBm, -60 */
    -25,        /* ADT_Int16 worstExpectedERLdB */
    -3,         /* RxSaturateLeveldBm, -3, 6 */
    1,          /* ADT_Int16 NoiseReduction1Setting */
    0,          /* ADT_Int16 NoiseReduction2Setting */
    1,          /* CNGEnable */
    0,          /* fixedGaindB10 */

    /* TxAGC Parameters */

    0,          /* ADT_Int8 AGCEnable          */
    10,         /* ADT_Int8 AGCMaxGaindB       */
    10,         /* ADT_Int8 AGCMaxLossdB       */
    -15,        /* ADT_Int8 AGCTargetLeveldBm  */
    -40,        /* ADT_Int8 AGCLowSigThreshdBm */

    /* RxAGC Parameters */

    0,          /* ADT_Int8 AGCEnable */
    10,         /* ADT_Int8 AGCMaxGaindB */
    15,         /* ADT_Int8 AGCMaxLossdB */
    -10,        /* ADT_Int8 AGCTargetLeveldBm  */
    -40,        /* ADT_Int8 AGCLowSigThreshdBm */
    0,          /* RxBypass */
    0,          /* ADT_Int16 maxTrainingTimeMSec */
    -40,        /* trainingRxNoiseLeveldBm */
    0,          /* ADT_Int16 pTxEqualizer */
    0,          /* mipsMemReductionSetting */
    0,          /* mipsReductionSetting2 */
    0           /* reserved */
};

const char * const short_options = "hvc:bds:g:";

const struct option long_options[] = {

    { "help",            0, NULL, 'h' },
    { "version",         0, NULL, 'v' },
    { "card",            1, NULL, 'c' },
    { "bypass",          0, NULL, 'b' },
    { "dump",            0, NULL, 'd' },
    { "period-size",     1, NULL, 'e' },
    { "buffer-size",     1, NULL, 'u' },
    { "sample-rate",     1, NULL, 'r' },
    { "bits-per-sample", 1, NULL, 'i' },
    { "source",          1, NULL, 's' },
    { "gpio",            1, NULL, 'g' },
    { NULL,              0, NULL,  0  }
};

static void show_usage(int err)
{
    printf("Usage: gytaecd [options]...\n");
    printf("Run echo cancellation algorithm for handfree mode.\n\n");
    printf("Options:\n");
    printf("  -h, --help                 show uages and exit\n");
    printf("  -v, --version              show version number and exit\n");
    printf("  -c, --card=DEVICENAME      the sound card device name, \n");
    printf("                             like \"default\" or \"plughw:0,0\"\n");
    printf("  -b, --bypass               run as bypass mode\n");
    printf("  -d, --dump                 dump into pcm file\n");
    printf("      --period-size=SIZE     frames per period by SIZE\n");
    printf("      --buffer-size=SIZE     frames per buffer by SIZE\n");
    printf("      --sample-rate=NUM      sample rate by NUM, like 8000,16000,44100\n");
    printf("      --bits-per-sample=NUM  bits per sample by NUM, like 8,12,16\n");
    printf("  -s  --source=SOURCE        handfree signal from SOURCE, \n");
    printf("                             0: gpio, 1: fifo, 2: signal\n");
    printf("  -g  --gpio=NUM             handfree gpio pin NUM\n");
    printf("\n");

    exit(err);
}

static void show_version(void)
{
    printf("version %d.%d.%d\n", VER_MAJOR, VER_MINOR, VER_PATCH);
    exit(0);
}

static void get_options(int argc, char *argv[])
{
    int option;

    while ((option = getopt_long(argc, argv, short_options, long_options, NULL)) != -1)
    {
        //printf(" >> option=%c, optopt=%c, optarg=%s\n", option, optopt, optarg);

        switch (option)
        {
        case 'h':  /* help */
            show_usage(0);
            break;
        case 'v':  /* version */
            show_version();
            break;
        case 'c':  /* card */
            //printf("#card#\n");
            strncpy(g_aec.device_name, optarg, 16);
            break;
        case 'b':  /* bypass */
            //printf("#bypass#\n");
            g_aec.bypass_mode = 1;
            break;
        case 'd':  /* dump */
            //printf("#dump#\n");
            g_aec.is_ec_dump = 1;
            break;
        case 'e':  /* period-size */
            //printf("#period-size#\n");
            g_aec.pcm.period_size = atoi(optarg);
            break;
        case 'u':  /* buffer-size */
            //printf("#buffer-size#\n");
            g_aec.pcm.buffer_size = atoi(optarg);
            break;
        case 'r':  /* sample-rate */
            //printf("#sample-rate#\n");
            g_aec.pcm.sample_rate = atoi(optarg);
            break;
        case 'i':  /* bits-per-sample */
            //printf("#bits-per-sample#\n");
            break;
        case 's':  /* source */
            //printf("#source#\n");
            g_aec.signal_src  = atoi(optarg);
            break;
        case 'g':  /* gpio */
            //printf("#gpio#\n");
            g_aec.gpio_number = atoi(optarg);
            break;
        case ':':
        case '?':
            //printf("(E) option invalid\n");
            exit(-1);
        default:
            show_usage(-1);
            break;
        }
    }
}

void LOG(const char *format, ...)
{
    time_t cur_time = time(NULL);
    struct tm *vt = localtime(&cur_time);
    fprintf(log_file, "[%d-%02d-%02d %02d:%02d:%02d] ", 
            vt->tm_year+1900, vt->tm_mon+1, vt->tm_mday, 
            vt->tm_hour, vt->tm_min, vt->tm_sec );

    int count = 0;
    va_list args;

    va_start(args, format);
    vfprintf(log_file, format, args);
    va_end(args);

    fflush(log_file);
}

static void delay(void)
{
    sleep(1);
}

static int gpio_init(void)
{
    LOG("(I) gpio init ...\n");

    int fd, ret;
    char path[64], buf[32];

    /* (1) Export gpio in sysfs */
    sprintf(path, "/sys/class/gpio/export");
    fd = open(path, O_WRONLY);
    if (fd < 0)
    {
        printf("(E) Open %s failed\n", path);
        return -1;
    }
    sprintf(buf, "%d", g_aec.gpio_number);
    write(fd, buf, strlen(buf));
    close(fd);

    /* (2) Set gpio direction */
    sprintf(path, "/sys/class/gpio/gpio%d/direction", g_aec.gpio_number);
    fd = open(path, O_RDWR);
    if (fd < 0) 
    {
        LOG("(E) Open %s failed!\n", path);
        return -1;
    }
    write(fd, "in", 2);
    close(fd);

    /* (3) Set gpio edge mode for interrupt */
    sprintf(path, "/sys/class/gpio/gpio%d/edge", g_aec.gpio_number);
    fd = open(path, O_RDWR);
    if (fd < 0) 
    {
        LOG("(E) Open %s failed!\n", path);
        return -1;
    }
    write(fd, "both", 4);
    close(fd);

    /* (4) Get gpio value */
    sprintf(path, "/sys/class/gpio/gpio%d/value", g_aec.gpio_number);
    handfree_fd = open(path, O_RDONLY);
    if(handfree_fd < 0) 
    {
        LOG("(E) Open %s failed!\n", path);
        return -1;
    }

    fds[0].fd = handfree_fd;
    fds[0].events = POLLPRI;

    ret = poll(fds, 1, 0);   /* first poll to clean residue r-event */
    if(ret == -1) 
    {
        LOG("(E) Poll on gpio value files failed!\n");
        close(fds[0].fd);
        return -1;
    }

    return 0;
}

static int fifo_init(void)
{
    int ret;

    LOG("(I) access %s\n", FIFO_NAME);

    if(access(FIFO_NAME, F_OK) == -1) {

        ret = mkfifo(FIFO_NAME, 0666);
        if(ret != 0) {
            LOG("(E) mkfifo hands free fifo (IPC) failed!\n");
            return -1;
        }
        LOG("(I) mkfifo okay.\n");
    }

	// If fifo writer not existed, it will be block...
    handfree_fd = open(FIFO_NAME, O_RDONLY);
    LOG("(I) handfree_fd: %d\n", handfree_fd);

    if(handfree_fd < 0) {
        LOG("(E) Open file hands free fifo (IPC) failed!\n");
        return -1;
    }
    LOG("(I) Open file hands free fifo (IPC) successlly!\n");

	return 0;
}

static void signal_handle(int sig);

static int signal_init(void)
{
	signal(SIGHUP, SIG_IGN);
	signal(SIGUSR1, signal_handle);
	signal(SIGUSR2, SIG_IGN);

	return 0;
}

static void gpio_handle(void)
{
    int ret;
    char value;

    ret = poll(fds, 1, -1);   /* poll on 1 file and wait forever */
    if(ret < 0) 
    {
        LOG("(E) gpioHandle : Poll on gpio value files failed!\n");
        return;
    }
    else if(0 == ret) 
    {
        LOG("gpioHandle : Poll nothing, bye");
        return;
    }
    
    LOG("(D) gpioHandle : Poll ready, ret = %d\n", ret);

    if(fds[0].revents & POLLPRI)
    {
        ret = lseek(handfree_fd, 0, SEEK_SET);
        if(ret == -1) 
        {
            LOG("(E) errror lseek on handfree_fd5.\n");
            return;
        }

        ret = read(handfree_fd, &value, 1);
        if(ret == -1) 
        {
            LOG("(E) errror read on fd.\n");
            return;
        }
        LOG("(D) Hand-free GPIO value change -> %c\n", value);

        if('0' == value) 
        {
            g_aec.enabled = true;
            sem_post(&handfree_sem);
            LOG("(I) AEC is enable.\n");
        }
        else if ('1' == value) 
        {
            g_aec.enabled = false;
            LOG("(I) AEC is disable.\n");
        }
    }
}

static void fifo_handle(void)
{
    int ret;
    char buffer[1];
	bzero(buffer, sizeof(buffer));

	// 这里要阻塞
    ret = read(handfree_fd, buffer, 1);

    if(-1 == ret) {
        LOG("(E) fifoHandle : Read fifo failed!\n");
        return ;
    }

	LOG("(I) fifoHandle : Read fifo come on! %d\n", (int)buffer[0]);

	/*
	 * 0 : Handfree button was normal.
	 * 1 : Handfree button was pressed.
	 */
	if(1 == (int)buffer[0]) {
		//AECG4_ADT_reset(g_aec.hAEC, &AEC_Params);
		g_aec.enabled = true;
		LOG("(I) Hands free button was pressed. AEC is enable.\n");
	}
	else if (0 == (int)buffer[0]) {
		g_aec.enabled = false;
		LOG("(I) Hands free button was normal. AEC is disable.\n");
	}
}

static void signal_handle(int sig)
{
	LOG("(I) signalHandle: catched a signal: %d\n", sig);

    g_aec.enabled = true;
}

static void default_params_init(void)
{
    g_aec.pcm_src  = PCMALSA;
    g_aec.pcm_dest = PCMALSA;
    g_aec.bypass_mode = 0;
    strncpy(g_aec.device_name, SND_CARD_NAME, 16);

    g_aec.is_ec_dump = 0;

    g_aec.signal_src  = 2;
    g_aec.gpio_number = 5;

    g_aec.pcm.period_size = FRAMES_PER_PERIOD;
    g_aec.pcm.buffer_size = FRAMES_PER_BUFFER;
    g_aec.pcm.sample_rate = SAMPLE_RATE;
}

static int initAECEngine()
{
    g_aec.hAEC = NULL;
    g_aec.cleanfile = NULL;
    g_aec.echofile = NULL;
    g_aec.reffile = NULL;
    g_aec.stereofile = NULL;

    AEC_Params.frameSize = g_aec.pcm.period_size;
    AEC_Params.samplingRate = g_aec.pcm.sample_rate;
    AEC_Params.maxAudioFreq = g_aec.pcm.sample_rate / 2;
    //AEC_Params.fixedBulkDelayMSec = 0;

    if((g_aec.hAEC = AECG4_ADT_create(0, &AEC_Params)) == 0) {

        g_aec.bypass_mode = 1;
        LOG("(E) AECG4_ADT_Create(): error, entering bypass mode\n");
        return -1;
    }
    LOG("(I) AECG4_ADT_Create() OK\n");

    if(g_aec.is_ec_dump)
    {
        g_aec.echofile  = fopen("/tmp/pcmecho.raw", "w");
        g_aec.reffile   = fopen("/tmp/pcmref.raw", "w");
        g_aec.cleanfile = fopen("/tmp/pcmclean.raw", "w");
        g_aec.stereofile= fopen("/tmp/pcmstereo.raw", "w");
    }

    return 0;
}

/*
*******************************************************************************
* Description: 将声卡初始化为播放模式
*
* Arguments  : outputOnly         count of parameters
*              playbackDev         parameters
*
* Returns    : true          Succeed.
*              false         Failed.
*******************************************************************************
*/
bool initALSAPlayback(bool outputOnly, char *playbackDev)
{
    snd_pcm_t *pcm_handle;
    snd_pcm_hw_params_t *hwparams;
    snd_pcm_sw_params_t *swparams;

    int exact_rate;
    int period_size = g_aec.pcm.period_size;
    int buffer_size = g_aec.pcm.buffer_size;

    int error, mode;

    // modified by luhuadong at 20170323
    LOG("(I) initALSAPlayback  buffer_size = %d\n", buffer_size);

    /* Allocate the snd_pcm_hw_params_t structure on the stack. */
    snd_pcm_hw_params_alloca(&hwparams);
    if (hwparams == NULL)
    {
        LOG("(E) Error allocate memory for hwparams\n");
        return false;
    }

    /* Open PCM. The last parameter of this function is the mode. */
    /* If this is set to 0, the standard mode is used. Possible   */
    /* other values are SND_PCM_NONBLOCK and SND_PCM_ASYNC.       */
    /* If SND_PCM_NONBLOCK is used, read / write access to the    */
    /* PCM device will return immediately. If SND_PCM_ASYNC is    */
    /* specified, SIGIO will be emitted whenever a period has     */
    /* been completely processed by the soundcard.                */

    if(outputOnly)
        mode=0;
    else
        mode=SND_PCM_NONBLOCK;

    if ((error = snd_pcm_open(&pcm_handle, playbackDev, SND_PCM_STREAM_PLAYBACK, 0)) < 0)
    {
        LOG("(E) Open %s PCM device failed, [%d] %s\n", playbackDev, error, snd_strerror(error));
        return(false);
    }

    /* Init hwparams with full configuration space */
    if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0)
    {
        LOG("(E) Can not configure this PCM device.\n");
        return(false);
    }

    //fprintf(stderr,"aft InitALSAPlayback 4 \n");
    /* Set access type. This can be either    */
    /* SND_PCM_ACCESS_RW_INTERLEAVED or       */
    /* SND_PCM_ACCESS_RW_NONINTERLEAVED.      */
    /* There are also access types for MMAPed */
    /* access, but this is beyond the scope   */
    /* of this introduction.                  */
    if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
        LOG("(E) Error setting access.\n");
        return(false);
    }

    /* Set sample format */
    if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0) {
        LOG("(E) Error setting format.\n");
        return(false);
    }

    /* Set sample rate. If the exact rate is not supported */
    /* by the hardware, use nearest possible rate.         */
    exact_rate = g_aec.pcm.sample_rate;
    if (snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0) < 0) {
        LOG("(E) Error setting rate.\n");
        return(false);
    }
    if (exact_rate != g_aec.pcm.sample_rate) {
        LOG("(W) The rate %d Hz is not supported by your hardware.\n ==> Using %d Hz instead.\n",
               g_aec.pcm.sample_rate, exact_rate);
    }

    /* Set number of channels */
    if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2) < 0) {
        LOG("(E) Error setting channels.\n");
        return(false);
    }

    /* Set period size. */
    if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, (snd_pcm_uframes_t*)&period_size, 0) < 0) {
        LOG("(E) Error setting periods.\n");
        return(false);
    }
    LOG("(I) Period size set to %d\n", period_size);


    if (error=snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams,  (snd_pcm_uframes_t*)&buffer_size) < 0)
    {
        LOG("(E) Error setting buffersize. %s\n", snd_strerror(error));
        return(false);
    }
    LOG("(I) playback Buffer size set to %d\n", buffer_size);

    /* Apply HW parameter settings to */
    /* PCM device and prepare device  */
    if (snd_pcm_hw_params(pcm_handle, hwparams) < 0)
    {
        LOG("(E) Error setting HW params.\n");
        return(false);
    }

	LOG("(D) pcm_handle = %p\n", pcm_handle);
    g_aec.pcm.handle_playback = pcm_handle;


    snd_pcm_uframes_t val;
    snd_pcm_sw_params_malloc(&swparams);
    if (swparams == NULL)
    {
        LOG("(E) Error allocate memory for swparams");
        return false;
    }
    snd_pcm_sw_params_current(pcm_handle, swparams);
    snd_pcm_sw_params_get_start_threshold(swparams, &val);

    return true;
}

/*
*******************************************************************************
* Description: 将声卡初始化为捕获模式
*
* Arguments  : argc         count of parameters
*              argv         parameters
*
* Returns    : == 0         Succeed.
*              <  0         Failed.
*******************************************************************************
*/
bool initALSACapture(char *captureDev)
{
    int exact_rate;
    int period_size = g_aec.pcm.period_size;
    int buffer_size = g_aec.pcm.buffer_size;

    int error;
    char *pcm_name;
    snd_pcm_t *pcm_handle;
    snd_pcm_hw_params_t *hwparams;
    snd_pcm_sw_params_t *swparams;
    pcm_name = strdup(g_aec.device_name);

    LOG("(I) initALSACapture  buffer_size = %d\n", buffer_size);

    /* Allocate the snd_pcm_hw_params_t structure on the stack. */
    snd_pcm_hw_params_alloca(&hwparams);


    /* Open PCM. The last parameter of this function is the mode. */
    /* If this is set to 0, the standard mode is used. Possible   */
    /* other values are SND_PCM_NONBLOCK and SND_PCM_ASYNC.       */
    /* If SND_PCM_NONBLOCK is used, read / write access to the    */
    /* PCM device will return immediately. If SND_PCM_ASYNC is    */
    /* specified, SIGIO will be emitted whenever a period has     */
    /* been completely processed by the soundcard.                */
    if ((error = snd_pcm_open(&pcm_handle, captureDev, SND_PCM_STREAM_CAPTURE, 0)) < 0)
    {
        LOG("(E) Open %s capture device failed, [%d] %s\n", captureDev, error, snd_strerror(error));
        return(false);
    }

    /* Init hwparams with full configuration space */
    if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0)
    {
        LOG("(E) Can not configure this PCM device.\n");
        return(false);
    }

    /* Set access type. */
    if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
        LOG("(E) Error setting access.\n");
        return(false);
    }

    /* Set sample format */
    if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0) {
        LOG("(E) Error setting format.\n");
        return(false);
    }

    /* Set sample rate. If the exact rate is not supported */
    /* by the hardware, use nearest possible rate.         */
    exact_rate = g_aec.pcm.sample_rate;
    if (snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0) < 0) {
        LOG("(E) Error setting rate.\n");
        return(false);
    }
    if (exact_rate != g_aec.pcm.sample_rate) {
        LOG("(W) The rate %d Hz is not supported by your hardware.\n ==> Using %d Hz instead.\n",
               g_aec.pcm.sample_rate, exact_rate);
    }

    /* Set number of channels */
    if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2) < 0) {
        LOG("(E) Error setting channels.\n");
        return(false);
    }

    /* Set period size. */
    if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, (snd_pcm_uframes_t*)&period_size, 0) < 0) {
        LOG("(E) Error setting periods.\n");
        return(false);
    }
    LOG("(I) Period size set to %d\n", period_size);

    /* Set buffer size (in frames). The resulting latency is given by */
    /* latency = periodsize * periods / (rate * bytes_per_frame)     */
    if (error=snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams,  (snd_pcm_uframes_t*)&buffer_size) < 0)
    {
        LOG("(E) Error setting buffersize. %s\n", snd_strerror (error));
        return(false);
    }
    LOG("(I) capture Buffer size set to %d\n", buffer_size);

    /* Apply HW parameter settings to */
    /* PCM device and prepare device  */
    if (snd_pcm_hw_params(pcm_handle, hwparams) < 0)
    {
        LOG("(E) Error setting HW params.\n");
        return(false);
    }

    g_aec.pcm.handle_capture = pcm_handle;

    snd_pcm_sw_params_malloc(&swparams);
    snd_pcm_sw_params_current(pcm_handle, swparams);
    snd_pcm_uframes_t val;
    snd_pcm_sw_params_get_start_threshold(swparams, &val);

    return true;
}

/*
*******************************************************************************
* Description: This function is the entry of program.
*
* Arguments  : outputOnly
*              captureDev
*              playbackDev
*
* Returns    : true          Succeed.
*              false         Failed.
*******************************************************************************
*/
bool initAudio(bool outputOnly, char *captureDev, char *playbackDev)
{
    system("amixer -q set 'Capture Mux' LINE_IN &");
    LOG("(C) Audio 'Capture Mux' set to LINE_IN.\n");

    if(!initALSAPlayback(outputOnly, playbackDev)) {
        LOG("(E) Audio playback init failed\n");
        return false;
    }
    LOG("(I) Audio playback init ok\n");

    if(!outputOnly) 
    {
        if(!initALSACapture(captureDev)) {
            LOG("(E) Audio capture init failed\n");
            return false;
        }
        LOG("(I) Audio capture init ok\n");
    }
    return true;
}

static int xrunRecovery(snd_pcm_t *handle, int error)
{
    switch(error)
    {
    case -EPIPE:    // Buffer Over-run
        if (error = snd_pcm_prepare(handle) < 0) {
            LOG("(E) Buffer overrrun/underrun cannot be recovered, snd_pcm_prepare fail: %s\n", snd_strerror(error));
        }
        return 0;
        break;

    case -ESTRPIPE: //suspend event occurred
        //EAGAIN means that the request cannot be processed immediately
        while ((error = snd_pcm_resume(handle)) == -EAGAIN)
            sleep(1);// wait until the suspend flag is clear

        if (error < 0) // error case
        {
            if (error = snd_pcm_prepare(handle) < 0) {
                LOG("(E) Suspend cannot be recovered, snd_pcm_prepare fail: %s\n", snd_strerror(error));
            }
        }
        return 0;
        break;

    case -EBADFD: //Error PCM descriptor is wrong
        break;

    default:
        break;
    }
    return error;
}

static bool shutdownAECEngine()
{
    if (g_aec.hAEC) {
        AECG4_ADT_delete(g_aec.hAEC);
        g_aec.hAEC= NULL;
    }

    if(g_aec.is_ec_dump)
    {
        if (g_aec.echofile)
            fclose(g_aec.echofile);

        if (g_aec.reffile)
            fclose(g_aec.reffile);

        if (g_aec.cleanfile)
            fclose(g_aec.cleanfile);

        if (g_aec.stereofile)
            fclose(g_aec.stereofile);
    }

    return true;
}

static bool shutdownAudio()
{
    if(g_aec.pcm.handle_playback)
        snd_pcm_close(g_aec.pcm.handle_playback);

    if(g_aec.pcm.handle_capture)
        snd_pcm_close(g_aec.pcm.handle_capture);

    return true;
}

static void playback_entry(void *params)
{
    int ret, i;
    bool restartOutput = false;
    snd_pcm_uframes_t size = g_aec.pcm.period_size;

    ret = snd_pcm_link(g_aec.pcm.handle_capture, g_aec.pcm.handle_playback);

    snd_pcm_prepare(g_aec.pcm.handle_playback);

    //prefill output buffer to prevent xflows
    memset(out_stereo, 0, sizeof(out_stereo));
    ret = snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
    if (ret < 0)
    {
        LOG("(W) #1 snd_pcm_writei error (%s)", snd_strerror(ret));
        xrunRecovery(g_aec.pcm.handle_playback, ret);
    }
    ret = snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
    if (ret < 0)
    {
        LOG("(W) #1 snd_pcm_writei error (%s)", snd_strerror(ret));
        xrunRecovery(g_aec.pcm.handle_playback, ret);
    }
    ret = snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
    if (ret < 0)
    {
        LOG("(W) #1 snd_pcm_writei error (%s)", snd_strerror(ret));
        xrunRecovery(g_aec.pcm.handle_playback, ret);
    }
#if 0
    ret = snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
    if (ret < 0)
    {
        LOG("(D) #2 snd_pcm_writei error (%s)", snd_strerror(ret));
    }
#endif

    prctl(PR_SET_NAME, "playback");
#if 1
    struct sched_param param;
    param.sched_priority = sched_get_priority_max(SCHED_FIFO);
    pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
#endif
    LOG("(I) Run playback thread\n");

    while (1)
    {
#if 0
        if(restartOutput)
        {
            //restarting, write some silence data to output to prefil buffer and prevent underrun
            LOG("(W) Restarting output\n");
            snd_pcm_prepare(g_aec.pcm.handle_playback);
                    
            memset(out_stereo, 0, sizeof(out_stereo));
            for(i=0;i<1;i++)
            {
                snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
                //snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
                //snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
            }
            restartOutput=false;
        }
#endif
        sem_wait(&data_sem); /* wait output data */
#if 0
        //pthread_mutex_lock(&data_lock);
        ret = snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
        //pthread_mutex_unlock(&data_lock);

        if (ret == -EAGAIN)
        {
            //pthread_mutex_lock(&data_lock);
            snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
            //pthread_mutex_unlock(&data_lock);
        }
        else if (ret < 0)
        {
            LOG("(W) 2# alsa write error (%s)\n", snd_strerror(ret));
            xrunRecovery(g_aec.pcm.handle_playback, ret);
            restartOutput=true;
        }

        if ( ret >= 0 && ret != size )
        {
            LOG("(W) Short on samples played: %d\n", ret);
        }
#endif
    }
}

static void capture_entry(void *params)
{
    int i, j, ret;
    bool restartOutput = false;
    snd_pcm_uframes_t size = g_aec.pcm.period_size;
    
    short int RxOut[size];
    ADT_Int16 echo[size];
    ADT_Int16 ref[size];
    ADT_Int16 clean[size];
    ADT_Int16 stereo[size * 2];

	// mask the signal in thread, but it doesn't work.
	if(g_aec.signal_src == HANDFREE_SIGNAL) 
    {
		sigset_t sigset;

		sigemptyset(&sigset);
		sigaddset(&sigset, SIGUSR1);
		pthread_sigmask(SIG_BLOCK, &sigset, NULL);
	}

    snd_pcm_prepare(g_aec.pcm.handle_capture);

    prctl(PR_SET_NAME, "capture");
#if 1
    struct sched_param param;
    param.sched_priority = sched_get_priority_max(SCHED_FIFO);
    pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
#endif
    LOG("(I) Run capture thread\n");

    while(1) 
    {
        sem_wait(&handfree_sem);  /* wait handfree signal */

        snd_pcm_prepare(g_aec.pcm.handle_playback);

        //prefill output buffer to prevent xflows
        memset(out_stereo, 0, sizeof(out_stereo));
        ret = snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
        if (ret < 0)
        {
            LOG("(W) #1 snd_pcm_writei error (%s)", snd_strerror(ret));
            xrunRecovery(g_aec.pcm.handle_playback, ret);
        }
        ret = snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
        if (ret < 0)
        {
            LOG("(W) #1 snd_pcm_writei error (%s)", snd_strerror(ret));
            xrunRecovery(g_aec.pcm.handle_playback, ret);
        }
        ret = snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
        if (ret < 0)
        {
            LOG("(W) #1 snd_pcm_writei error (%s)", snd_strerror(ret));
            xrunRecovery(g_aec.pcm.handle_playback, ret);
        }

        while(g_aec.enabled)
        {
            ret = snd_pcm_readi(g_aec.pcm.handle_capture, stereo, size);
#if 1
            if (ret < 0) 
            {
                LOG("(W) alsa read error (%s)\n", snd_strerror(ret));

                if (xrunRecovery(g_aec.pcm.handle_capture, ret) < 0) 
                {
                    LOG("(W) microphone: Write error: %s\n", snd_strerror(ret));
                }

                //memset(out_stereo, 0, sizeof(out_stereo));
                continue;
            }
#endif
            /* Capture frames is ok */
            if(ret != size)
            {
                LOG("(W) Short on samples captured: %d\n", ret);
            }

            /* split stereo into left and right channel */

            for(i=0,j=0; i<size*2; i=i+2)
            {
                memcpy(ref+j,  stereo+i,   sizeof(ADT_Int16)); /* Left */
                memcpy(echo+j, stereo+1+i, sizeof(ADT_Int16)); /* Right */
                j++;
            }

            /* dump pcm data to file */
            if(g_aec.is_ec_dump)
            {
                fwrite(stereo, sizeof(ADT_Int16), size*2, g_aec.stereofile); // 2 channels
                fwrite(echo, sizeof(ADT_Int16), size, g_aec.echofile);
                fwrite(ref, sizeof(ADT_Int16), size, g_aec.reffile);
            }

            /* bypass or not */
            if(g_aec.bypass_mode)
            {
                //pthread_mutex_lock(&data_lock);

                for(i=0,j=0; j<size; i=i+2)
                {
                    memcpy(out_stereo+i, echo+j, sizeof(ADT_Int16));
                    memset(out_stereo+1+i, 0, sizeof(ADT_Int16));
                    j++;
                }
                //pthread_mutex_unlock(&data_lock);
            }
            else
            {
                /* AEC process */
                AECG4_ADT_apply(g_aec.hAEC, (short int *)ref, RxOut, (short int *)echo, (short int *)clean);

                if(g_aec.is_ec_dump)
                {
                    fwrite(clean, sizeof(ADT_Int16), size, g_aec.cleanfile);
                }

                //pthread_mutex_lock(&data_lock);

                for(i=0,j=0;j<size;i=i+2)
                {
                    memcpy(out_stereo+i,clean+j,sizeof(ADT_Int16));
                    //memcpy(out_stereo+i+1,clean+j,sizeof(ADT_Int16));
                    memset(out_stereo+1+i, 0, sizeof(ADT_Int16));
                    j++;
                }
                //pthread_mutex_unlock(&data_lock);
            }
            
            //sem_post(&data_sem);
            ret = snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
            if (ret == -EAGAIN)
            {
                LOG("(W) PCM write again");
                snd_pcm_writei(g_aec.pcm.handle_playback, out_stereo, size);
            }
            else if (ret < 0)
            {
                LOG("(W) 2# alsa write error (%s)\n", snd_strerror(ret));
                xrunRecovery(g_aec.pcm.handle_playback, ret);
            }

        } // while(g_aec.enabled)
        
    } // while(1)

    LOG("(I) Shutting down Audio");
    shutdownAudio();

    LOG("(I) Shutting down ADT AEC Engine");
    shutdownAECEngine();
}

#ifdef DAEMON
#define NOFILE        256

static int init_daemon(const char *pname, int facility)
{
    int pid, i;

    /* (1)屏蔽一些有关控制终端操作的信号 */
    signal(SIGTTOU, SIG_IGN);
    signal(SIGTTIN, SIG_IGN);
    signal(SIGTSTP, SIG_IGN);
    signal(SIGHUP , SIG_IGN);

    /* (2)在后台运行 */
    if (pid = fork())
    {
        exit(EXIT_SUCCESS);
    }
    else if (pid < 0)
    {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    /* (3)脱离控制终端和进程组 */
    setsid();

    /* (4)禁止进程重新打开控制终端 */
    if (pid = fork())
    {
        exit(EXIT_SUCCESS);
    }
    else if (pid < 0)
    {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    /* (5)关闭打开的文件描述符 */
    for (i=0; i<NOFILE; ++i)
    {
        close(i);
    }

    open("/dev/null", O_RDONLY);
    open("/dev/null", O_RDWR);
    open("/dev/null", O_RDWR);

    /* (6)改变当前工作目录 */
    //chdir("/tmp");

    /* (7)重设文件创建掩码 */
    umask(0);

    /* (8)处理SIGCHLD信号 */
    signal(SIGCHLD, SIG_IGN);

    /* (9)建立与日志守候进程的联系 */
    openlog(pname, LOG_PID, facility);
    syslog(LOG_INFO, "Hello, ADT_AEC!");

    return 0;
}
#endif

static bool is_singleton(void)
{
	int fd = -1;
	char buf[32];

	fd = open(DEFAULT_LOCK_FILE, O_WRONLY | O_CREAT, 0666);
	if(fd < 0) {
		printf("(E) Open %s failed.\n", DEFAULT_LOCK_FILE);
		return false;
	}

	struct flock lock;
	bzero(&lock, sizeof(lock));

	if(fcntl(fd, F_GETLK, &lock) < 0) {
		printf("(E) Fail to fcntl F_GETLK.\n");
		return false;
	}

	lock.l_type = F_WRLCK;
	lock.l_whence = SEEK_SET;

	if(fcntl(fd, F_SETLK, &lock) < 0) {
		printf("(E) Fail to fcntl F_SETLK.\n");
		return false;
	}

	pid_t pid = getpid();
	int len = snprintf(buf, 32, "%d\n", (int)pid);

	// Write pid to the file, don't close
	if(-1 == write(fd, buf, len)) {
		printf("(E) Fail to write pid into file.\n");
		return false;
	}

	return true;
}

static struct handfree_src source_table[] = {

    { "gpio",   gpio_init,   gpio_handle },
    { "fifo",   fifo_init,   fifo_handle },
    { "signal", signal_init, delay },
};

int main(int argc, char *argv[])
{
    int ret;
    char *ini_name;
    char *playback_dev, *capture_dev;
    pthread_t capture_tid, playback_tid;

    /* (1) 初始化配置参数 */
    default_params_init();
    get_options(argc, argv);

    /* (2) 守护进程模式 */
#ifdef DAEMON
    init_daemon(argv[0], LOG_KERN);
#endif

    /* (3) 单例模式 */
    if(!is_singleton()) {

        printf("(W) The program is already running.\n");
        return -1;
    }

    /* (4) 打开日志文件 */
    chdir("/tmp");

    log_file = fopen(DEFAULT_LOG_FILE, "w");
    if (log_file == NULL)
        return -1;

    LOG("************ ADT AEC ************\n");

    /* (5) 安装免提信号 */
    ret = source_table[g_aec.signal_src].init();
    if (ret < 0) 
    {
        LOG("(E) Signal source init failed.\n");
        exit(EXIT_FAILURE);
    }

    g_aec.enabled = false;
    LOG("(I) Signal source init succeed.\n");

    /* (6) 初始化声卡设备("default" or "plughw:0,0") */
    playback_dev = strdup(g_aec.device_name);
    capture_dev  = strdup(g_aec.device_name);

    int count = 0;
    while (!initAudio(0, capture_dev, playback_dev))
    {
        LOG("(W) Audio initialization failed [%d]\n", count++);
        if(count > 3)
        {
            LOG("(E) Audio initialization failed .Exit! \n");
            goto __exit;
        }
        sleep(1);
    }

    /* (7) 初始化回声消除引擎 */
    ret = initAECEngine();
    if(ret < 0) {
        goto __exit;
    }

    system("amixer -q set 'PCM' 192 &");
    LOG("(I) Audio 'PCM' set to 192.\n");

    /* (8) 初始化同步互斥 */
    ret = pthread_mutex_init(&data_lock, NULL);
    if (ret != 0)
    {
        LOG("(E) Mutex init failed\n");
        goto __exit;
    }

    ret = sem_init(&handfree_sem, 0, 0);
    if (ret != 0)
    {
        LOG("(E) Handfree semaphore init failed\n");
        goto __exit;
    }

    ret = sem_init(&data_sem, 0, 0);
    if (ret != 0)
    {
        LOG("(E) PCM data semaphore init failed\n");
        goto __exit;
    }

    /* (9) 创建 capture & playback 线程 */

    if(pthread_create(&capture_tid, NULL,(void *) &capture_entry, NULL)) {

        LOG("(E) Create capture thread failed\n");
        goto __exit;
    }

    if(pthread_create(&playback_tid, NULL, (void *)&playback_entry, NULL)) {

        LOG("(E) Create playback thread failed\n");
        goto __exit;
    }

    LOG("(I) frameProcessThread created.\n");
    LOG("(I) adt_aec init finished.\n");

    /* (10) 监听免提信号 */
    prctl(PR_SET_NAME, "listen");
#if 1
    pthread_setschedparam(pthread_self(),SCHED_OTHER, NULL);
#else
    struct sched_param param;
    param.sched_priority = sched_get_priority_max(SCHED_FIFO);
    sched_setscheduler(getpid(), SCHED_FIFO, &param);
#endif

    /* 主线程作为后台线程，检测免提信号 */
    while(1) {

        #ifdef PRINT_ERL
            IAECG4_Status Status;
            AECG4_ADT_control(g_aec.hAEC,IAECG4_GETSTATUS,(IAECG4_Status *) &Status);
            LOG("(D) erldB10BestEstimate %d\n", Status.erldB10BestEstimate);
            LOG("(D) worstPerBinERLdB10BestEstimate %d\n", Status.worstPerBinERLdB10BestEstimate);
        #endif

        source_table[g_aec.signal_src].handler();
	}

    void *p;
    pthread_join(capture_tid, &p);
    LOG("(E) Capture thread exit: %s\n", (char *)p);
    pthread_join(playback_tid, &p);
    LOG("(E) Playback thread exit: %s\n", (char *)p);

__exit:
    if (playback_tid)
        pthread_cancel(playback_tid);
    if (capture_tid)
        pthread_cancel(capture_tid);

    pthread_mutex_destroy(&data_lock);
    sem_destroy(&handfree_sem);
    sem_destroy(&data_sem);

    shutdownAudio();
    shutdownAECEngine();

    fclose(log_file);

	return 0;
}
